/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package motiveai.components;

import com.jme3.app.state.AbstractAppState;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import motiveai.factors.Environment;
import motiveai.factors.EnvironmentDefinition;
import motiveai.learn.LearningType;
import motiveai.learn.AbstractLearningChooser;
import motiveai.learn.LearnCentralUnit;

/**
 *
 * @author oxplay
 */
public class MotiveAI<T extends Agent> extends AbstractAppState {

    private final static Logger logger = Logger.getLogger(MotiveAI.class.getName());
    private LearnCentralUnit learnCentralUnit;
    private Set<SensorEntity<T>> sensors = new HashSet<SensorEntity<T>>();
    private ArrayList<T> agents = new ArrayList<T>();
    private Map<String, AgentGroup> groupsList = new HashMap<String, AgentGroup>();
    private float updateRateCount = 0f;
    private float updateRateMax = 0f;
    private int maxCountLearn = 100;
    private EnvironmentDefinition environmentDefinition;

    public void setAgents(ArrayList<T> agents) {
        this.agents = agents;
    }

    public ArrayList<T> getAgents() {
        return agents;
    }

    public Map<String, AgentGroup> getGroupsList() {
        return groupsList;
    }

    public AgentGroup getGroup(String name) {
        return this.groupsList.get(name);
    }

    public void addGroup(AgentGroup group) {
        this.groupsList.put(group.getName(), group);
    }

    public void removeGroup(AgentGroup group) {
        this.groupsList.remove(group.getName());
    }

    public EnvironmentDefinition getEnvironmentDefinition() {
        return environmentDefinition;
    }

    /*
     * Return global learn count left.
     */
    public long learnCountLeft() {
        return learnCentralUnit.learnCountLeft();
    }

    /*
     * Return learn count left for given boolean Factor.
     */
    public long learnCountLeft(Environment environment) {
        return learnCentralUnit.learnCountLeft(environment);
    }

    /*
     * Return integer of behaviour counting/remember Maximum
     */
    public int getMaxCountLearn() {
        return maxCountLearn;
    }

    /*
     * Set max for counting of behaviour execution and remember it's performance
     */
    public void setMaxCountLearn(int maxCountLearn) {
        this.maxCountLearn = maxCountLearn;
    }

    /*
     * Get UpdateRate of Manager.
     */
    public float getUpdateRate() {
        return updateRateMax;
    }

    /*
     * Set UpdateRate of Manager.
     * 1f = 1 second
     * it affect both: sensor update and behaviour execution
     */
    public void setUpdateRate(float updateRateMax) {
        this.updateRateMax = updateRateMax;
    }

    /*
     * Consruct Manager and
     * Set one of default BasedDecision
     * it affect on decision to choose behaviour
     */
    public MotiveAI(EnvironmentDefinition environmentDefinition, LearningType basedDecision, int maxCountLearn) {
        this.environmentDefinition = environmentDefinition;
        this.maxCountLearn = maxCountLearn;
        learnCentralUnit = new LearnCentralUnit(this);
        learnCentralUnit.setBasedDecision(basedDecision);
        logger.setLevel(Level.OFF);
    }

    /*
     * Consruct Manager and
     * Set a user extended CustomBasedDecision
     * Only for advanced users.
     */
    public MotiveAI(AbstractLearningChooser customBasedDecision, int maxCountLearn) {
        this.maxCountLearn = maxCountLearn;
        learnCentralUnit = new LearnCentralUnit(this);
        learnCentralUnit.setCustomBasedDecision(customBasedDecision);
        logger.setLevel(Level.OFF);
    }

    /*
     * Set one of default BasedDecision
     * it affect on decision to choose behaviour
     */
    public void setBasedDecision(LearningType basedDecision) {
        learnCentralUnit.setBasedDecision(basedDecision);
    }

    /*
     * Set a user extended CustomBasedDecision
     * Only for advanced users.
     */
    public void setCustomBasedDecision(AbstractLearningChooser customBasedDecision) {
        learnCentralUnit.setCustomBasedDecision(customBasedDecision);
    }

    /*
     * Add agent to Manager and then execute agent onAdd() method
     * Agent class need to be same like in Template
     */
    public void addAgent(T agent) {
        agents.add(agent);
        agent.initAgentEnvironment(environmentDefinition);
        agent.setManager(this);
        agent.onAdd();
    }

    /*
     * Remove agent from Manager and then execute agent onRemove() method
     */
    public void removeAgent(T agent) {
        agents.remove(agent);
        agent.setManager(null);
        agent.onRemove();
    }

    /*
     * Remove all agents from Manager and execute agent onRemove() method
     */
    public void removeAllAgents() {
        for (Iterator<T> it = agents.iterator(); it.hasNext();) {
            T agent = it.next();
            agent.remove();
            it.remove();
        }
    }

    /*
     * Remove Sensor from that manager
     */
    public void removeSensor(SensorEntity<T> sensor) {
        sensors.remove(sensor);
    }

    /*
     * Add Sensor to use it for all agents in that manager
     */
    public void addSensor(SensorEntity<T> sensor) {
        sensors.add(sensor);
    }

    /*
     * Register Behaviour for letting know about able behave.
     * remember! behaviour class need to be public and extend BehaviourEntity.
     */
    public void registerBehaviour(Class behaviour) {
        learnCentralUnit.registerBehaviour(behaviour);
    }

    private void updateSensors(float tpf) {
        for (Iterator<T> it = agents.iterator(); it.hasNext();) {
            T agent = it.next();
            Environment agentEnvironment = agent.getAgentEnvironment();
            for (Iterator<SensorEntity<T>> it2 = sensors.iterator(); it2.hasNext();) {
                SensorEntity<T> sensorEntity = it2.next();
                sensorEntity.update(tpf, agent, agentEnvironment);
            }
            agentEnvironment.updateData();
        }
    }

    private void updateAgent(T agent) {
        Environment agentEnvironment = agent.getAgentEnvironment();
        if (agentEnvironment != null) {
            learnCentralUnit.setActualEnvironment(agentEnvironment);
            BehaviourEntity behaviour = learnCentralUnit.getBestBehaviour(agent);
            if (behaviour != null) {
                try {
                    behaviour.initialize(agentEnvironment);
                    agent.behave(behaviour, maxCountLearn);
                    logger.log(Level.INFO, "choosed Behaviour: {0} performance: {1}", new Object[]{behaviour.getClass().getSimpleName(), behaviour.getPerformance()});
                    agentEnvironment.addBehaviourMemory(behaviour);
                    agentEnvironment = behaviour.getBehaviourDestEnvironment();
                    agent.setAgentEnvironment(agentEnvironment);
                } catch (CloneNotSupportedException ex) {
                    logger.log(Level.SEVERE, null, ex);
                }
            }
        }
    }

    private void updateAgents(float tpf) {
        logger.log(Level.INFO, "updateAgents");
        for (Iterator<AgentGroup> it = groupsList.values().iterator(); it.hasNext();) {
            AgentGroup agentGroup = it.next();
            agentGroup.onUpdate(agentGroup.agentList);
        }
        for (Iterator<T> it = agents.iterator(); it.hasNext();) {
            T agent = it.next();
            if (agent.needToRemove()) {
                it.remove();
            } else if (agent.update(tpf)) {
                updateAgent(agent);
            }
        }
    }

    /*
     * Update Loop for manager
     */
    @Override
    public void update(float tpf) {
        updateRateCount += tpf;
        if (updateRateCount >= updateRateMax) {
            updateSensors(updateRateCount);
            updateAgents(updateRateCount);
            updateRateCount = 0;
        }
    }

    /*
     * Execute Simulation for actual agents.
     */
    public void executeSimulation(int simulationCount) {
        learnCentralUnit.setSimulation(true);
        for (int i = 0; i < simulationCount; i++) {
            for (Iterator<T> it = agents.iterator(); it.hasNext();) {
                T agent = it.next();
                Environment agentEnvironment = agent.getAgentEnvironment();
                for (Iterator<SensorEntity<T>> it2 = sensors.iterator(); it2.hasNext();) {
                    SensorEntity<T> sensorEntity = it2.next();
                    sensorEntity.update(sensorEntity.getStimuliFactor(), agent, agentEnvironment);
                }
                agentEnvironment.updateData();
            }
            for (Iterator<AgentGroup> it = groupsList.values().iterator(); it.hasNext();) {
                AgentGroup agentGroup = it.next();
                agentGroup.onUpdate(agentGroup.agentList);
            }
            for (Iterator<T> it = agents.iterator(); it.hasNext();) {
                T agent = it.next();
                if (agent.needToRemove()) {
                    it.remove();
                } else {
                    agent.update(agent.getAwakeTime());
                    updateAgent(agent);
                }
            }
        }
        learnCentralUnit.setSimulation(false);
    }

    /*
     * Use system output to print Environment Data.
     */
    public void printDebugInformation() {
        System.out.println("---Manager DebugInformation---");
        System.out.println("Manager updateRate: " + updateRateMax);
        System.out.println("learnCountLeft: " + learnCountLeft());
        learnCentralUnit.printDebugInformation();
    }
}
